//============================================================================
// Copyright (C) 2003-2013 Brett R. Jones
//
// Issued to public domain 2013
//
// brettjones1900@gmail.com
// http://www.p2panarchy.com
//============================================================================

#include <string.h>
#include <stdarg.h>
#include "VxUtilLib.h"
#include "sqlite3.h"
#include "DbBase.h"
#include "VxFileUtil.h"

//---------------------------------------------------------------------------
//=== constructor ===//
DbBase::DbBase() 
	: m_bDbInitialized(0)
	, m_iDbVersion(0)
	, m_poDb(0)
{
	// empty
}
//---------------------------------------------------------------------------
//=== destructor ===//
DbBase::~DbBase()
{
}

//---------------------------------------------------------------------------
//! Initialize the database.. if doesn't exist then call DbCreateDatabase and DbCreateTables
RCODE DbBase::DbStartup( int iDbVersion, const char * pDbName )
{
	RCODE rc = 0;
    char tmpDir[ MAX_PATH ];
    char* pTemp;

	vx_assert( pDbName );
	m_strDbFileName = pDbName;
	m_iDbVersion = iDbVersion;
        
    // create paths and database if unnecessary
    if( ! VxFileExists( m_strDbFileName.c_str() ) )
    {
        //create db 
		// make directory for db if needed
        strcpy( tmpDir, m_strDbFileName.c_str() );
        pTemp = strrchr( tmpDir, '/' );
        if( NULL != pTemp )
        {
			pTemp[0] = '\0';
			VxMakeDirectory( tmpDir );
		}
      
        rc = onCreateDatabase(iDbVersion);
        if( 0 != rc )
        {
            log_msg( 0,"DbBase:Cannot create database %s\n", m_strDbFileName.c_str() );
            return rc;
        }
    }
	else
	{
		// check the version .. upgrade if necessary
		int iOldDbVersion = readDatabaseVersion();
		if( iOldDbVersion != m_iDbVersion )
		{
			rc = onUpgradeDatabase( iOldDbVersion, m_iDbVersion );
		}
	}
    m_bDbInitialized = TRUE;
    log_msg( LOG_VERBOSE, "DbBase: %s initialized\n", m_strDbFileName.c_str() );
    return 0;
}

//---------------------------------------------------------------------------
//! shutdown the database
RCODE DbBase::DbShutdown( void )
{
	return 0;
}

//---------------------------------------------------------------------------
//! archive database into given directory
RCODE DbBase::DbArchive( const char * pArchiveDirectory, BOOL bClearExistingDb )
{
	/*
	char ArchiveDir[MAX_PATH];
	char ArchiveDbPath[MAX_PATH];
	
	if (!g_bAcctDbInitialized)
	{
		if(0 != StartupAcctDb(DEFAULT_EXEC_PATH))
		{
			log_msg(SRV_DB_ACCT_ERR, "ArchiveDay:Storage Failed to Init\n");
			return -1;
		}
	}
	sprintf(ArchiveDir, "%s%s", g_atcAcctDbExecPath, DEFAULT_ACCT_ARCHIVE_DIR);
	if(0 != VxMakeDirectory(ArchiveDir))
	{
		log_msg(SRV_DB_ACCT_ERR,"ArchiveDay:Cannot create archive directory %s\n", ArchiveDir);
		return -1;
	}
	char TimeStamp[100];
	VxGetTimeStamp(TimeStamp, 100);
	sprintf(ArchiveDbPath, "%s%sacct_db%s", g_atcAcctDbExecPath, DEFAULT_ACCT_ARCHIVE_DIR, TimeStamp);
	if ( 0 == (VxCopyFile( g_atcAcctDbPath, ArchiveDbPath )))
	{
		VxDeleteFile( g_atcAcctDbPath );
	}
	else
	{
		log_msg(SRV_DB_ACCT_ERR, "AchiveAcctDb:Error copying db file\n");
		return -1;
	}
	if(0 != StartupAcctDb(g_atcAcctDbExecPath))
	{
		log_msg(SRV_DB_ACCT_ERR, "ArchiveAcctDb:Storage Failed to Init New Db\n");
		return -1;
	}
	*/
	return 0;	
}

//---------------------------------------------------------------------------
//! create initial database
RCODE DbBase::onCreateDatabase( int iDbVersion )
{
	sqlite3 *db;
    RCODE rc = sqlite3_open( m_strDbFileName.c_str(), &db);
    if( SQLITE_OK != rc )
    {
        log_msg( LOG_ERROR, "DbCreateDatabase:ERROR %d Unable to create database %s\n", rc, m_strDbFileName.c_str() );
        sqlite3_close(db);
        return rc;
    }
    // close database.. Create tables will reopen
    sqlite3_close(db);
	writeDatabaseVersion( iDbVersion );

    // make tables in database
    rc = onCreateTables( iDbVersion );
	if( rc )
    {
        log_msg( LOG_ERROR, "CreateDataBase:ERROR %d Unable to create Table in db %s\n", rc, m_strDbFileName.c_str() );
		VxDeleteFile( m_strDbFileName.c_str() );
    }
	// create version table
    return rc;
}
//---------------------------------------------------------------------------
//! upgrade db from old version to new version
RCODE DbBase::onUpgradeDatabase(int iOldDbVersion, int iNewDbVersion)
{
	RCODE rc = onDeleteTables( iOldDbVersion );
	if( 0 == rc )
	{
		rc = onCreateTables( iNewDbVersion );
	}
	if( rc )
	{
		log_msg( LOG_ERROR, "onUpgradeDatabase:ERROR %d in db %s\n", rc, m_strDbFileName.c_str() );
	}
	return rc;
}

//---------------------------------------------------------------------------
//! open the database
RCODE DbBase::DbOpen( void )
{
    int retval;
	vx_assert( m_strDbFileName.size() );
    retval = sqlite3_open( m_strDbFileName.c_str(), &m_poDb );
    if (!(SQLITE_OK == retval))
    {
        log_msg( LOG_ERROR, "DbBase:Unable to open db %s\n", m_strDbFileName.c_str() );
        sqlite3_close(m_poDb);
		m_poDb = NULL;
        return -1;
    }
	vx_assert( m_poDb );
	return 0;
}
//---------------------------------------------------------------------------
//! close the database
RCODE DbBase::DbClose( void )
{
	if( m_poDb )
	{
        sqlite3_close(m_poDb);
		m_poDb = NULL;
		return 0;
	}
    log_msg( 0, "DbBase:Tried to close already closed db %s\n", m_strDbFileName.c_str() );
	return -1;
}
//---------------------------------------------------------------------------
//! read database version from version table 
int DbBase::readDatabaseVersion( void )
{
	DbOpen();
	int iVersion =0;
	sqlite3_stmt * poSqlStatement = NULL;
	int iResult = sqlite3_prepare_v2( m_poDb, "SELECT * FROM DBBASE_VERSION", (int)strlen(  "SELECT * FROM DBBASE_VERSION" )+1, &poSqlStatement, NULL );
	if( SQLITE_OK != iResult ) 
	{
		log_msg( 0, "DbBase::readDatabaseVersion: error %s\n", sqlite3_errmsg(m_poDb) );
		return 0;
	}
	if( SQLITE_ROW == sqlite3_step( poSqlStatement ) )
	{
		iVersion = sqlite3_column_int(poSqlStatement, 0 );
	}
	sqlite3_finalize(poSqlStatement);
	sqlite3_exec(m_poDb,"END",NULL,NULL,NULL);
	DbClose();
	return iVersion;
}

//---------------------------------------------------------------------------
//! write database version to version table 
RCODE DbBase::writeDatabaseVersion( int iDbVersion )
{
	char as8Buf[ 256 ];
	if( DbTableExists( "DBBASE_VERSION" ))
	{
		sprintf( as8Buf, "UPDATE DBBASE_VERSION db_version (%d)", iDbVersion );
		return SqlExec(as8Buf);
	}
	else
	{
		SqlExec("CREATE TABLE DBBASE_VERSION (db_version INTEGER)");
		sprintf( as8Buf, "INSERT INTO DBBASE_VERSION (db_version) VALUES (%d)", iDbVersion );
		return SqlExec(as8Buf);
	}
}
//! return true if table exists
bool DbBase::DbTableExists( const char * pTableName )
{
	DbOpen();
	bool bHasTable = false;
	sqlite3_stmt * poSqlStatement = NULL;
	char as8Buf[ 256 ];
	sprintf( as8Buf, "SELECT name FROM sqlite_master WHERE type='table' AND name='%s'", pTableName);
    sqlite3_prepare_v2( m_poDb, as8Buf, (int)strlen( as8Buf )+1, &poSqlStatement, NULL );
	if( SQLITE_OK == sqlite3_prepare_v2( m_poDb, as8Buf, (int)strlen( as8Buf )+1, &poSqlStatement, NULL ) ) 
	{
		if( SQLITE_ROW == sqlite3_step( poSqlStatement ) )
		{
			bHasTable = true;
		}
	}
	DbClose();

	return bHasTable;
}

//---------------------------------------------------------------------------
//! execute Sql statement
RCODE DbBase::SqlExec( const char * pSqlString, ... )
{
    char SQL_Statement[4095];

	va_list arg_ptr;
	va_start(arg_ptr, pSqlString);
	#ifdef ANDROID_PLATFORM
		vsnprintf(SQL_Statement, 4095, pSqlString, arg_ptr);
	#else
		vsnprintf(SQL_Statement, 4095, pSqlString, (char *)arg_ptr);
	#endif
	SQL_Statement[4094] = 0;
	va_end(arg_ptr);
	return SqlExec( SQL_Statement );
}

//---------------------------------------------------------------------------
//! execute Sql statement
RCODE DbBase::SqlExec( char * SQL_Statement )
{
    char *SQL_Error;
    int retval;
	RCODE rc = DbOpen();
	if( 0 == rc )
	{
		retval = sqlite3_exec( m_poDb, SQL_Statement, NULL, NULL, &SQL_Error );
		if (!(SQLITE_OK == retval))
		{
			log_msg( 0, "DbBase:sqlite3_exec:%s", SQL_Error );
			sqlite3_free(SQL_Error);
			DbClose();
			return -1;
		}
	}
	rc = DbClose();
    return rc;
}
//---------------------------------------------------------------------------
//! start query and use DbCursor to access column's data.. be sure to call DbCursor.close() when done
DbCursor * DbBase::StartQuery( char * pSqlString )
{
	vx_assert( pSqlString );
	RCODE rc = DbOpen();
	if( 0 == rc )
	{
		sqlite3_stmt * poSqlStatement = NULL;
		int iResult = sqlite3_prepare_v2( m_poDb, pSqlString, (int)strlen( pSqlString ), &poSqlStatement, NULL );
		if( SQLITE_OK != iResult ) 
		{
			log_msg( 0, "DbBase::StartDataQuery: error %s\n", sqlite3_errmsg(m_poDb) );
			return NULL;
		}
		DbCursor * poCursor = new DbCursor();
		poCursor->m_poDbBase = this;
		poCursor->m_poStmt = poSqlStatement;
		return poCursor;

	}
	return NULL;
}


//---------------------------------------------------------------------------
//! start query and use DbCursor to access column's data.. be sure to call DbCursor.close() when done
DbCursor * DbBase::StartQuery( const char * pSqlString, ... )
{
	char SQL_Statement[4095];

	va_list arg_ptr;
	va_start(arg_ptr, pSqlString);
#ifdef ANDROID_PLATFORM
	vsnprintf(SQL_Statement, 4095, pSqlString, arg_ptr);
#else
	vsnprintf(SQL_Statement, 4095, pSqlString, (char *)arg_ptr);
#endif
	SQL_Statement[4094] = 0;
	va_end(arg_ptr);
	return StartQuery( SQL_Statement );
}	



//---------------------------------------------------------------------------
//! get the row id of the last inserted row
S64 DbBase::GetLastInsertId( void )
{
	if( m_poDb )
	{
		return sqlite3_last_insert_rowid( m_poDb );
	}
	return 0;
}

DbCursor::DbCursor()
	: m_poDbBase(0)
	, m_poStmt(0)
{

}

//! close database and delete ourself
void DbCursor::close( void )
{
	int iResult = sqlite3_finalize( m_poStmt );
	if( SQLITE_OK != iResult ) 
	{
		log_msg( LOG_ERROR, "DatabaseClass::CloseQuery: error %s\n", sqlite3_errmsg(m_poDbBase->m_poDb) );
	}
	delete this;
}

//! fetch next row from table
bool DbCursor::getNextRow( void )
{
	int iResult = sqlite3_step( m_poStmt );
	switch( iResult ) 
	{
	case SQLITE_ROW:
		return true;
		break;
	case SQLITE_DONE:
		return false;
		break;
	default:
		log_msg( LOG_ERROR, "DbBase::GetDbRow: error %s\n", sqlite3_errmsg(m_poDbBase->m_poDb) );
		break;
	}
	return false;
}

//! return 8 bit integer from column
U8 DbCursor::getByte( int iColumnIdx )
{
	return (U8)sqlite3_column_int( m_poStmt, iColumnIdx );
}
//! return 32 bit integer from column
S32 DbCursor::getS32( int iColumnIdx )
{
	return (S32)sqlite3_column_int( m_poStmt, iColumnIdx );
}
//! return 64 bit integer from column
S64 DbCursor::getS64( int iColumnIdx )
{
	return sqlite3_column_int64( m_poStmt, iColumnIdx );
}
//! return float from column
F32 DbCursor::getF32( int iColumnIdx )
{
	return (F32)sqlite3_column_double( m_poStmt, iColumnIdx );
}
//! return float from column
F64 DbCursor::getF64( int iColumnIdx )
{
	return (F64)sqlite3_column_double( m_poStmt, iColumnIdx );
}
//! return string from column
const char * DbCursor::getString(int iColumnIdx )
{
	return (const char *)sqlite3_column_text( m_poStmt, iColumnIdx );
}
//! return blob from column.. 
//! if( piMaxLen != null ) then return length of blob in piMaxLen 
void *	 DbCursor::getBlob( int iColumnIdx, int * piRetLen )
{
	if( NULL != piRetLen )
	{
		*piRetLen =  sqlite3_column_bytes( m_poStmt, iColumnIdx );
	}
	return (void *)sqlite3_column_blob( m_poStmt, iColumnIdx );

}


